1 /** 2 Copyright: Copyright (c) 2018, Joakim Brännström. All rights reserved. 3 License: MPL-2 4 Author: Joakim Brännström (joakim.brannstrom@gmx.com) 5 6 This Source Code Form is subject to the terms of the Mozilla Public License, 7 v.2.0. If a copy of the MPL was not distributed with this file, You can obtain 8 one at http://mozilla.org/MPL/2.0/. 9 10 This module can deduce the system compiler flags, if possible, from the 11 compiler specified in a CompileCommand. 12 13 The module assumes that during an execution the system flags for a compiler do 14 not change thus they can be cached. This avoids having to invoke the compiler 15 more than necessary. 16 17 This module exists for those times that: 18 * a cross-compiler which uses other system headers than the hosts system 19 compiler. E.g. clang-tidy do not know *what* these are thus this module 20 discoveres them and provide them. 21 * multiple compiler versions are used in a build and each have different 22 headers. 23 */ 24 module compile_db.system_compiler; 25 26 import logger = std.experimental.logger; 27 import std.algorithm : countUntil, map; 28 import std.array : empty, array; 29 import std..string : startsWith, stripLeft, splitLines; 30 31 import compile_db : CompileCommand, shouldEqual, shouldBeIn; 32 33 @safe: 34 35 struct Compiler { 36 string value; 37 alias value this; 38 } 39 40 struct SystemIncludePath { 41 string value; 42 alias value this; 43 } 44 45 /** Execute and inspect the compiler for the system includes. 46 * 47 * Note that how the compilers are inspected is hard coded. 48 */ 49 SystemIncludePath[] deduceSystemIncludes(CompileCommand cmd, const Compiler compiler) { 50 return deduceSystemIncludes(cmd.command, compiler); 51 } 52 53 /// ditto 54 SystemIncludePath[] deduceSystemIncludes(const string[] cmd, const Compiler compiler) { 55 import std.process : execute; 56 57 if (cmd.empty || compiler.empty) 58 return null; 59 60 if (auto v = compiler in cacheSysIncludes) { 61 return *v; 62 } 63 64 auto args = systemCompilerArg(cmd, compiler); 65 66 auto res = execute(args); 67 if (res.status != 0) { 68 logger.tracef("Failed to inspect the compiler for system includes: %-(%s %)", args); 69 logger.trace(res.output); 70 return null; 71 } 72 73 auto incls = parseCompilerOutput(res.output); 74 cacheSysIncludes[compiler] = incls; 75 76 return incls; 77 } 78 79 private: 80 81 string[] systemCompilerArg(const string[] cmd, const Compiler compiler) { 82 string[] args = ["-v", "/dev/null", "-fsyntax-only"]; 83 if (auto v = language(compiler, cmd)) { 84 args = [v] ~ args; 85 } 86 if (auto v = sysroot(cmd)) { 87 args ~= v; 88 } 89 return [compiler.value] ~ args; 90 } 91 92 SystemIncludePath[] parseCompilerOutput(const string output) { 93 auto lines = output.splitLines; 94 const start = lines.countUntil("#include <...> search starts here:") + 1; 95 const end = lines.countUntil("End of search list."); 96 if (start == 0 || end == 0 || start > end) 97 return null; 98 99 return lines[start .. end].map!(a => SystemIncludePath(a.stripLeft)).array; 100 } 101 102 SystemIncludePath[][Compiler] cacheSysIncludes; 103 104 // assumes that compilers adher to the gcc and llvm commands use of --sysroot / 105 // -isysroot. 106 // depends on the fact that CompileCommand.Command always splits e.g. a 107 // --isysroot=foo to ["--sysroot", "foo"]. 108 const(string[]) sysroot(const string[] cmd) { 109 foreach (flag; ["--sysroot", "-isysroot"]) { 110 auto index = cmd.countUntil!(a => a.startsWith(flag)); 111 if (index >= 0 && (index + 2) <= cmd.length) 112 return cmd[index .. index + 2]; 113 } 114 115 return null; 116 } 117 118 @("shall extract --sysroot and its argument") 119 unittest { 120 ["foo", "--sysroot", "bar"].sysroot.shouldEqual(["--sysroot", "bar"]); 121 ["foo", "-isysroot", "bar"].sysroot.shouldEqual(["-isysroot", "bar"]); 122 } 123 124 // assumes that compilers adher to the gcc and llvm commands of using -xLANG 125 string language(Compiler compiler, const string[] cmd) { 126 import std.path : baseName; 127 import std.typecons : No; 128 129 auto index = cmd.countUntil!(a => a.startsWith("-x")) + 1; 130 if (index > 0 && index < cmd.length) 131 return cmd[index]; 132 133 switch (compiler.baseName) { 134 case "cc": 135 case "clang": 136 case "gcc": 137 return "-xc"; 138 case "c++": 139 case "clang++": 140 case "g++": 141 return "-xc++"; 142 default: 143 } 144 145 return null; 146 } 147 148 @("shall parse the system flags") 149 unittest { 150 import std.typecons : Tuple; 151 152 // arrange 153 immutable compiler_output = `Using built-in specs. 154 COLLECT_GCC=gcc 155 COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/7/lto-wrapper 156 OFFLOAD_TARGET_NAMES=nvptx-none 157 OFFLOAD_TARGET_DEFAULT=1 158 Target: x86_64-linux-gnu 159 Configured with: ../src/configure -v --with-pkgversion='Ubuntu 7.3.0-27ubuntu1~18.04' --with-bugurl=file:///usr/share/doc/gcc-7/README.Bugs --enable-languages=c,ada,c++,go,brig,d,fortran,objc,obj-c++ --prefix=/usr --with 160 -gcc-major-version-only --program-suffix=-7 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-n 161 ls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-libmpx --enable-plugin --enable-d 162 efault-pie --with-system-zlib --with-target-system-zlib --enable-objc-gc=auto --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic 163 --enable-offload-targets=nvptx-none --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu 164 Thread model: posix 165 gcc version 7.3.0 (Ubuntu 7.3.0-27ubuntu1~18.04) 166 COLLECT_GCC_OPTIONS='-v' '-fsyntax-only' '-mtune=generic' '-march=x86-64' 167 /usr/lib/gcc/x86_64-linux-gnu/7/cc1 -quiet -v -imultiarch x86_64-linux-gnu /dev/null -quiet -dumpbase null -mtune=generic -march=x86-64 -auxbase null -version -fsyntax-only -o /dev/null -fstack-protector-strong -Wformat 168 -Wformat-security 169 GNU C11 (Ubuntu 7.3.0-27ubuntu1~18.04) version 7.3.0 (x86_64-linux-gnu) 170 compiled by GNU C version 7.3.0, GMP version 6.1.2, MPFR version 4.0.1, MPC version 1.1.0, isl version isl-0.19-GMP 171 172 GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072 173 ignoring nonexistent directory "/usr/local/include/x86_64-linux-gnu" 174 ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/7/../../../../x86_64-linux-gnu/include" 175 #include "..." search starts here: 176 #include <...> search starts here: 177 /usr/lib/gcc/x86_64-linux-gnu/7/include/foo 178 /usr/local/include 179 /usr/lib/gcc/x86_64-linux-gnu/7/include-fixed 180 /usr/include/x86_64-linux-gnu 181 /usr/include 182 End of search list. 183 GNU C11 (Ubuntu 7.3.0-27ubuntu1~18.04) version 7.3.0 (x86_64-linux-gnu) 184 compiled by GNU C version 7.3.0, GMP version 6.1.2, MPFR version 4.0.1, MPC version 1.1.0, isl version isl-0.19-GMP 185 186 GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072 187 Compiler executable checksum: c8081a99abb72bbfd9129549110a350c 188 COMPILER_PATH=/usr/lib/gcc/x86_64-linux-gnu/7/:/usr/lib/gcc/x86_64-linux-gnu/7/:/usr/lib/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/7/:/usr/lib/gcc/x86_64-linux-gnu/ 189 LIBRARY_PATH=/usr/lib/gcc/x86_64-linux-gnu/7/:/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/7/../../../../lib/:/lib/x86_64-linux-gnu/:/lib/../lib/:/usr/lib/x86_64-linux-gnu/:/us 190 r/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/7/../../../:/lib/:/usr/lib/ 191 COLLECT_GCC_OPTIONS='-v' '-fsyntax-only' '-mtune=generic' '-march=x86-64'`; 192 193 // act 194 auto sysflags = parseCompilerOutput(compiler_output); 195 196 // assert 197 "/usr/lib/gcc/x86_64-linux-gnu/7/include/foo".shouldBeIn(sysflags); 198 "/usr/local/include".shouldBeIn(sysflags); 199 "/usr/lib/gcc/x86_64-linux-gnu/7/include-fixed".shouldBeIn(sysflags); 200 "/usr/include/x86_64-linux-gnu".shouldBeIn(sysflags); 201 "/usr/include".shouldBeIn(sysflags); 202 }